package com.example.demo.service.abs;

import cn.hutool.core.convert.ConverterRegistry;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @author chxbca
 */
public abstract class AbstractQueryService {

    private static final String LIKE = "like:";

    private static final String IN = "in:";

    private static final String LT = "lt:";

    private static final String GT = "gt:";

    protected <T> Specification<T> createSpecification(String q, Class<T> clazz) {
        Map<String, Object> requestMap = JSONUtil.toBean(q, new TypeReference<Map<String, Object>>() {
        }, false);
        return (Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) -> {
            List<Predicate> list = new ArrayList<>();
            for (Map.Entry<String, Object> object : requestMap.entrySet()) {
                String key = object.getKey();
                Field field = ReflectUtil.getField(clazz, key);
                if (field != null) {
                    Class<?> columnType = field.getType();
                    Predicate equal = cb.equal(root.get(key),
                            convert(object.getValue(), columnType));
                    list.add(equal);
                    continue;
                }
                if (key.startsWith(LIKE)) {
                    String subPreKey = StrUtil.subAfter(key, LIKE, false);
                    Field subField = ReflectUtil.getField(clazz, subPreKey);
                    if (subField != null) {
                        Predicate like = cb.like(root.get(subPreKey).as(String.class),
                                //因为索引文件具有 B-Tree 的最左前缀匹配特性，所以使用右模糊查询
                                object.getValue() + "%");
                        list.add(like);
                    }
                    continue;
                }
                if (key.startsWith(IN)) {
                    String subPreKey = StrUtil.subAfter(key, IN, false);
                    Field subField = ReflectUtil.getField(clazz, subPreKey);
                    if (subField != null) {
                        Class<?> type = subField.getType();
                        List<?> in = JSONUtil.parseArray(object.getValue()).toList(type);
                        list.add(root.get(subPreKey).in(in));
                    }
                    continue;
                }
                if (key.startsWith(LT)) {
                    String subPreKey = StrUtil.subAfter(key, LT, false);
                    Field subField = ReflectUtil.getField(clazz, subPreKey);
                    if (subField != null) {
                        Class<?> type = subField.getType();
                        Object typeInstance = convert(object.getValue(), type);
                        Predicate lt = cb.lessThanOrEqualTo(root.get(subPreKey),
                                convert(typeInstance, Comparable.class));
                        list.add(lt);
                    }
                    continue;
                }
                if (key.startsWith(GT)) {
                    String subPreKey = StrUtil.subAfter(key, GT, false);
                    Field subField = ReflectUtil.getField(clazz, subPreKey);
                    if (subField != null) {
                        Class<?> type = subField.getType();
                        Object typeInstance = convert(object.getValue(), type);
                        Predicate gt = cb.greaterThanOrEqualTo(root.get(subPreKey),
                                convert(typeInstance, Comparable.class));
                        list.add(gt);
                    }
                }
            }
            return cb.and(list.toArray(new Predicate[0]));
        };
    }

    private <T> T convert(Object obj, Class<T> tClass) {
        ConverterRegistry instance = ConverterRegistry.getInstance();
        return instance.convert(tClass, obj);
    }

}
